/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, AstTreeUtils, ts, ArkClass } from 'arkanalyzer';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import { RuleFix } from '../../model/Fix';
import { Rule } from '../../model/Rule';
import { FileMatcher, MatcherCallback, MatcherTypes } from '../../matcher/Matchers';
import { RuleListUtil } from "../../utils/common/DefectsList";
import Logger, { LOG_MODULE_TYPE } from "arkanalyzer/lib/utils/logger";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, "PreferFunctionTypeCheck");
const gMetaData: BaseMetaData = {
  severity: 3,
  ruleDocPath: "docs/prefer-function-type-check.md",
  description: "type only has a call signature, you should use a function type instead..",
};

export class PreferFunctionTypeCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private program: ts.Program;
  private typeChecker: ts.TypeChecker;
  private buildMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  // 缓存已经检查过的类型
  private checkedTypes = new Map<string, boolean>();

  // 缓存已分析过的代码
  private static readonly analyzedCode = new Map<string, boolean>();
  // 缓存已处理过的节点
  private static readonly processedNodes = new WeakMap<ts.Node, boolean>();
  // 缓存创建的 Program
  private static programCache: ts.Program | null = null;

  private getProgram(filePath: string): ts.Program {
    try {
      if (!PreferFunctionTypeCheck.programCache) {
        PreferFunctionTypeCheck.programCache = ts.createProgram({
          rootNames: [filePath],
          options: { target: ts.ScriptTarget.ESNext }
        });
      }
      return PreferFunctionTypeCheck.programCache;
    } catch (error) {
      logger.error(`Failed to create program: ${error instanceof Error ? error.message : 'Unknown error'}`);
      // 返回一个新的 Program 实例作为后备
      return ts.createProgram({ rootNames: [filePath], options: { target: ts.ScriptTarget.ESNext } });
    }
  }

  public registerMatchers(): MatcherCallback[] {
    const matchBuildCb: MatcherCallback = {
      matcher: this.buildMatcher,
      callback: this.check
    };
    return [matchBuildCb];
  }

  public check = (arkFile: ArkFile) => {
    const code = arkFile.getCode();
    if (!code) return;

    // 使用缓存避免重复分析相同的代码
    const codeHash = this.hashCode(code);
    if (PreferFunctionTypeCheck.analyzedCode.has(codeHash)) {
      return;
    }
    PreferFunctionTypeCheck.analyzedCode.set(codeHash, true);

    const astRoot = AstTreeUtils.getASTNode(arkFile.getName(), code);
    // 批量处理所有语句
    this.processStatements(astRoot.statements, arkFile, astRoot);

    // 文件处理完成后清理缓存
    PreferFunctionTypeCheck.clearCaches();
  }

  private processStatements(statements: ts.NodeArray<ts.Statement>, arkFile: ArkFile, astRoot: ts.SourceFile): void {
    const filePath = arkFile.getFilePath();
    if (!filePath) return;

    try {
      this.program = this.getProgram(filePath);
      this.typeChecker = this.program.getTypeChecker();

      for (const statement of statements) {
        // 使用 WeakMap 缓存已处理过的节点
        if (PreferFunctionTypeCheck.processedNodes.has(statement)) continue;
        PreferFunctionTypeCheck.processedNodes.set(statement, true);

        if (this.isRelevantNode(statement)) {
          this.checkPreferFunctionType(arkFile, statement, astRoot, this.typeChecker);
        }
      }
    } catch (error) {
      logger.error(`Error processing file: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  private hashCode(str: string): string {
    try {
      if (!str) return '0';
      let hash = 0;
      for (let i = 0; i < str.length; i++) {
        const char = str.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash;
      }
      return hash.toString();
    } catch {
      return '0'; // 返回默认值，避免崩溃
    }
  }

  private checkPreferFunctionType(arkFile: ArkFile, node: ts.Node, sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): void {
    try {
      // 快速类型检查
      if (!this.isRelevantNode(node)) return;
      // 使用缓存检查继承属性
      if (ts.isInterfaceDeclaration(node)) {
        const key = node.getText();
        if (this.checkedTypes.has(key)) {
          if (this.checkedTypes.get(key)) return;
        } else {
          const hasInherited = this.checkHeritageClauses(node, arkFile);
          this.checkedTypes.set(key, hasInherited);
          if (hasInherited) return;
        }
        this.checkInterfaceOrTypeAlias(arkFile, node, sourceFile, false);
      } else if (ts.isTypeAliasDeclaration(node)) {
        this.checkTypeAliasDeclaration(node, sourceFile);
      } else if (ts.isFunctionDeclaration(node)) {
        this.checkFunctionOrVariableDeclaration(node as ts.FunctionDeclaration, sourceFile);
      }
    } catch (error) {
      logger.error(`Error processing node: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  private isRelevantNode(node: ts.Node): boolean {
    return ts.isInterfaceDeclaration(node) ||
      ts.isTypeAliasDeclaration(node) ||
      ts.isFunctionDeclaration(node);
  }

  private analyzeCallSignatures(members: ts.NodeArray<ts.TypeElement>): {
    hasOnlyCallSignature: boolean;
    callSignatures: ts.CallSignatureDeclaration[];
    usesThisType: boolean;
  } {
    try {
      if (!members || !Array.isArray(members)) {
        return { hasOnlyCallSignature: false, callSignatures: [], usesThisType: false };
      }
      let hasOnlyCallSignature = true;
      const callSignatures: ts.CallSignatureDeclaration[] = [];
      let usesThisType = false;

      for (const member of members) {
        if (ts.isCallSignatureDeclaration(member)) {
          if (callSignatures.length === 1) {
            return { hasOnlyCallSignature: false, callSignatures: [], usesThisType: false };
          }
          callSignatures.push(member);
          usesThisType = usesThisType || this.checkUsesThisType(member);
        } else if (ts.isPropertySignature(member) || ts.isConstructSignatureDeclaration(member)) {
          hasOnlyCallSignature = false;
        }
      }

      return { hasOnlyCallSignature, callSignatures, usesThisType };
    } catch (error) {
      logger.error(`Failed to analyze call signatures: ${error instanceof Error ? error.message : 'Unknown error'}`);
      return { hasOnlyCallSignature: false, callSignatures: [], usesThisType: false };
    }
  }

  private checkHeritageClauses(node: ts.InterfaceDeclaration, arkFile: ArkFile): boolean {
    const hasHeritageClauses = node.heritageClauses;
    if (!hasHeritageClauses) return false;

    for (const clause of hasHeritageClauses) {
      if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;

      for (const type of clause.types) {
        const typeName = type.expression.getText();
        if (typeName === 'Function') continue;

        const aliasCls = arkFile.getClassWithName(typeName) as ArkClass;
        const code = aliasCls?.getCode();
        if (code && this.hasPropertySignature(code)) {
          return true;
        }
      }
    }
    return false;
  }

  // 正则表达式
  private static readonly PROPERTY_SIGNATURE_REGEX = /(?:interface|class)\s+\w+\s*{[^}]*(?:readonly\s+)?[_$a-zA-Z][_$\w]*\s*[?:]/;
  private static readonly RETURN_TYPE_REGEX_CACHE = new Map<string, RegExp>();

  private getReturnTypeRegex(returnTypeText: string): RegExp {
    try {
      if (!returnTypeText) return new RegExp(':', 'g');

      let regex = PreferFunctionTypeCheck.RETURN_TYPE_REGEX_CACHE.get(returnTypeText);
      if (!regex) {
        regex = new RegExp(`: ${this.escapeRegExp(returnTypeText)}`, "g");
        PreferFunctionTypeCheck.RETURN_TYPE_REGEX_CACHE.set(returnTypeText, regex);
      }
      return regex;
    } catch {
      return new RegExp(':', 'g'); // 返回一个基础的正则表达式作为后备
    }
  }

  private hasPropertySignature(code: string): boolean {
    // 使用正则表达式快速检查是否可能包含属性签名
    if (!PreferFunctionTypeCheck.PROPERTY_SIGNATURE_REGEX.test(code)) {
      return false;
    }

    // 只在可能包含属性签名时才进行AST解析
    const sourceFile = ts.createSourceFile("example.ts", code, ts.ScriptTarget.Latest, true);
    for (const statement of sourceFile.statements) {
      if (!ts.isInterfaceDeclaration(statement) && !ts.isClassDeclaration(statement)) continue;

      if (statement.members.some(member => ts.isPropertySignature(member))) {
        return true;
      }
    }
    return false;
  }

  private checkInterfaceOrTypeAlias(arkFile: ArkFile, node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration, sourceFile: ts.SourceFile, isExtendsFunction: boolean): void {
    const name = node.name?.getText(sourceFile);
    if (!name) {
      return;
    }
    let hasOnlyCallSignature = true;
    let callSignatures: ts.CallSignatureDeclaration[] = [];
    let usesThisType = false;
    if (ts.isInterfaceDeclaration(node)) {
      // 遍历接口的成员
      ts.forEachChild(node, (member) => {
        if (ts.isCallSignatureDeclaration(member)) {
          callSignatures.push(member);
          usesThisType = this.checkUsesThisType(member);
        } else if (ts.isPropertySignature(member) || ts.isConstructSignatureDeclaration(member)) {
          hasOnlyCallSignature = false;
        }
      });
    } else if (ts.isTypeAliasDeclaration(node)) {
      // 遍历类型别名的成员
      if (ts.isTypeLiteralNode(node.type)) {
        ts.forEachChild(node.type, (member) => {
          if (ts.isCallSignatureDeclaration(member)) {
            callSignatures.push(member);
            usesThisType = this.checkUsesThisType(member);
          } else if (ts.isPropertySignature(member) || ts.isConstructSignatureDeclaration(member)) {
            //属性签名
            hasOnlyCallSignature = false;
          }
        });
      }
    }
    if (callSignatures.length > 1) { return; }
    if (hasOnlyCallSignature && callSignatures.length > 0) {
      const callSignature = callSignatures[0];
      const callSignatureParent = callSignature.parent.getText();
      const interfaceName = node.name?.getText();
      // 将 interface 替换为 type
      let modifiedString = callSignatureParent.replace('interface', 'type');
      if (isExtendsFunction) {
        modifiedString = modifiedString.replace('extends Function', '');
      }
      const returnTypeText = callSignature.type?.getText(sourceFile) || 'void';
      const regex1 = new RegExp(`\\b${interfaceName}\\s*(<[^>]*>)?\\s*\\{`, 'g'); // 匹配 interfaceName 后面的 {，并捕获可选的泛型部分
      modifiedString = modifiedString.replace(regex1, `${interfaceName}$1 =`);
      modifiedString = modifiedString.replace(/\}+$/, ''); // 替换最后一位} 为空格
      // 使用正则表达式匹配 returnTypeText 前面的 :
      const parametersText = modifiedString.replace(
        this.getReturnTypeRegex(returnTypeText),
        `=> ${returnTypeText}`
      );
      const fixKeyword = parametersText;
      let errMesaging = 'interface only has a call signature, you should use a function type instead.'
      let isThis = false;
      if (usesThisType || returnTypeText === 'this') {
        errMesaging = `'this' refers to the function type ${name},did you intend to use a generic 'this' parameter liker`;
        isThis = true;
      }
      let defect = this.createDefect(callSignature, callSignature.getText(), errMesaging, isThis);
      let ruleFix = this.createFix(node, fixKeyword);
      if (usesThisType) {
        this.issues.push(new IssueReport(defect, undefined));
      } else {
        this.issues.push(new IssueReport(defect, ruleFix));
      }

    }
  }
  private escapeRegExp(text: string) {
    return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  }
  private checkTypeAliasDeclaration(node: ts.TypeAliasDeclaration, sourceFile: ts.SourceFile): void {
    if (ts.isIntersectionTypeNode(node.type)) {
      node.type.types.forEach(intersectionType => {
        if (ts.isTypeLiteralNode(intersectionType)) {
          this.reportTypeLiteral(node, intersectionType, sourceFile);
        }
      });
    } else if (ts.isUnionTypeNode(node.type)) {
      node.type.types.forEach(unionType => {
        if (ts.isTypeLiteralNode(unionType)) {
          this.reportTypeLiteral(node, unionType, sourceFile);
        }
      });
    } else if (ts.isTypeLiteralNode(node.type)) {
      let hasOnlyCallSignature = true;
      let callSignatures: ts.CallSignatureDeclaration[] = [];
      let usesThisType = false;
      ts.forEachChild(node.type, (child) => {
        if (ts.isCallSignatureDeclaration(child)) {
          callSignatures.push(child);
          usesThisType = this.checkUsesThisType(child);
        } else if (ts.isPropertySignature(child) || ts.isConstructSignatureDeclaration(child)) {
          hasOnlyCallSignature = false;
        }
      });
      if (callSignatures.length > 1) { return; }
      if (hasOnlyCallSignature && callSignatures.length > 0) {
        // 提取调用签名的参数和返回类型
        const callSignature = callSignatures[0];
        const callSignatureParent = callSignature.parent.getText();
        const modifiedString = callSignatureParent.replace(/\{/, '').replace(/\}$/, '');//删除{}
        const returnTypeText = callSignature.type?.getText(sourceFile) || 'void';
        // 使用正则表达式匹配 returnTypeText 前面的 :
        const parametersText = modifiedString.replace(
          this.getReturnTypeRegex(returnTypeText),
          `=> ${returnTypeText}`
        );
        const fixKeyword = parametersText;
        const errMesaging = `'this' refers to the function type ${node.name.getText()},did you intend to use a generic 'this' parameter liker`;
        let defect = this.createDefect(callSignature, callSignature.getText(), usesThisType ? errMesaging : this.metaData.description, usesThisType);
        let ruleFix = this.createFix(callSignature.parent, fixKeyword);
        if (usesThisType) {
          this.issues.push(new IssueReport(defect, undefined));
        } else {
          this.issues.push(new IssueReport(defect, ruleFix));
        }
      }
    }
  }

  private checkFunctionOrVariableDeclaration(node: ts.FunctionDeclaration | ts.VariableDeclaration, sourceFile: ts.SourceFile): void {
    if (ts.isFunctionDeclaration(node)) {
      node.parameters.forEach(param => this.checkParameterType(param, sourceFile));
    } else if (ts.isVariableDeclaration(node)) {
      this.checkParameterType(node, sourceFile);
    }
  }
  private checkParameterType(node: ts.ParameterDeclaration | ts.VariableDeclaration, sourceFile: ts.SourceFile): void {
    const type = node.type;
    if (type) {
      if (ts.isTypeLiteralNode(type)) {
        // 参数类型是类型字面量（可能包含调用签名）
        this.reportTypeLiteral(node, type, sourceFile);
      } else if (ts.isUnionTypeNode(type)) {
        // 参数类型是联合类型
        type.types.forEach(unionType => {
          if (ts.isTypeLiteralNode(unionType)) {
            this.reportTypeLiteral(node, unionType, sourceFile);
          }
        });
      }
    }
  }
  private checkUsesThisType(member: ts.CallSignatureDeclaration): boolean {
    let usesThisType = false;

    // 检查参数类型是否包含 'this'
    usesThisType = member.parameters.some(param => {
      return param.type !== undefined && ts.isThisTypeNode(param.type);
    });

    // 检查返回类型是否包含 'this'
    if (!usesThisType && member.type && ts.isTypeNode(member.type)) {
      if (ts.isUnionTypeNode(member.type)) {
        // 检查联合类型中的每个成员是否是 'this'
        usesThisType = member.type.types.some(type => ts.isThisTypeNode(type));
      } else if (ts.isTypeReferenceNode(member.type) && member.type.typeName.getText() === 'this') {
        usesThisType = true;
      }
    }

    return usesThisType;
  }

  private reportTypeLiteral(node: ts.Node, type: ts.TypeLiteralNode, sourceFile: ts.SourceFile): void {
    let name: string | undefined;
    if (ts.isTypeAliasDeclaration(node)) {
      name = node.name.getText(sourceFile);
    }
    let hasOnlyCallSignature = true;
    let callSignatures: ts.CallSignatureDeclaration[] = [];
    let usesThisType = false;
    ts.forEachChild(type, (child) => {
      if (ts.isCallSignatureDeclaration(child)) {
        callSignatures.push(child);
        usesThisType = this.checkUsesThisType(child);
      } else if (ts.isPropertySignature(child) || ts.isConstructSignatureDeclaration(child)) {
        hasOnlyCallSignature = false;
      }
    });
    if (callSignatures.length > 1) { return; }
    if (hasOnlyCallSignature && callSignatures.length > 0) {
      const errMesaging = `'this' refers to the function type ${name},did you intend to use a generic 'this' parameter liker`;
      const callSignature = callSignatures[0];
      const callSignatureParent = callSignature.parent.getText();
      const modifiedString = callSignatureParent.replace(/[{}]/g, '');
      const returnTypeText = callSignature.type?.getText(sourceFile) || 'void';
      let parametersText = modifiedString.replace(
        this.getReturnTypeRegex(returnTypeText),
        `=> ${returnTypeText}`
      );
      if (node.getText().includes('|') || node.getText().includes('&')) {
        parametersText = "(" + parametersText + ")"
      }
      const fixKeyword = parametersText;
      let defect = this.createDefect(callSignature, callSignature.getText(), usesThisType ? errMesaging : this.metaData.description, usesThisType);
      let ruleFix = this.createFix(callSignature.parent, fixKeyword);
      if (usesThisType) {
        this.issues.push(new IssueReport(defect, undefined));
      } else {
        this.issues.push(new IssueReport(defect, ruleFix));
      }
    }
  }

  private createDefect(node: ts.Node, keyword: string, errMesaging: string, isThis: boolean): Defects {
    const warnInfo = this.getLineAndColumn(node, isThis);
    const filePath = warnInfo.filePath;
    let lineNum = warnInfo.line;
    let startColum = warnInfo.startCol;
    let endColumn = warnInfo.endCol;
    const severity = this.rule.alert ?? this.metaData.severity;
    const defect = new Defects(lineNum, startColum, endColumn, errMesaging, severity,
      this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, true);
    this.defects.push(defect);
    RuleListUtil.push(defect);
    return defect;
  }
  private getLineAndColumn(node: ts.Node, isThis: boolean): { line: number; startCol: number; endCol: number; filePath: string } {
    const sourceFile = node.getSourceFile();
    let startIndex = 0;
    if (isThis) {
      startIndex = node.getText().indexOf('this')
    }
    const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
    const endCharacter = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).character;
    return {
      line: line + 1,
      startCol: character + 1 + startIndex,
      endCol: endCharacter + 1,
      filePath: sourceFile.fileName
    };
  }
  private createFix(child: ts.Node, code: string): RuleFix {
    return { range: [child.getStart(), child.getEnd()], text: code };
  }

  // 添加清理方法
  public static clearCaches(): void {
    try {
      PreferFunctionTypeCheck.analyzedCode.clear();
      PreferFunctionTypeCheck.RETURN_TYPE_REGEX_CACHE.clear();
      PreferFunctionTypeCheck.programCache = null;
    } catch (error) {
      logger.error(`Failed to clear caches: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }
}